package com.jaychen.spring.core.ioc;

import com.jaychen.spring.core.io.Resource;
import com.jaychen.spring.core.io.loader.ResourceLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

/**
 * xml的方式读取BeanDefinition
 *
 * @Author Jay Chen
 * @Date 2020/11/9 19:19
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(String location) throws Exception {
        Resource resource = getResourceLoader().getResource(location);
        InputStream inputStream = resource.getInputStream();
        doLoadBeanDefinitions(inputStream);
    }

    /**
     * 加载BeanDefinitions
     *
     * @param inputStream 文件流
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    private void doLoadBeanDefinitions(InputStream inputStream) throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        Document doc = docBuilder.parse(inputStream);
        // 解析bean
        registerBeanDefinitions(doc);
        inputStream.close();
    }

    /**
     * 注册bean
     *
     * @param doc
     */
    private void registerBeanDefinitions(Document doc) {
        Element root = doc.getDocumentElement();
        parseBeanDefinitions(root);
    }

    /**
     * 解析bean
     *
     * @param root
     */
    protected void parseBeanDefinitions(Element root) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                processBeanDefinition(ele);
            }
        }
    }

    /**
     * bean 处理
     *
     * @param ele
     */
    protected void processBeanDefinition(Element ele) {
        String name = ele.getAttribute("name");
        String className = ele.getAttribute("class");

        Map<String, BeanDefinition> map = getRegistry();
        if (map.get(name) != null) {
            throw new RuntimeException("Repeated loading bean name:" + name);
        }
        BeanDefinition beanDefinition = new BeanDefinition();
        // 设置bean class name
        beanDefinition.setBeanClassName(className);
        // 解析属性并注入
        processProperty(ele, beanDefinition);
        // 将解析完成的beanDefinition，加载进Map<String, BeanDefinition>
        // todo 注意此时只是解析了BeanDefinition
        // spring是在显示调用getBean时才会去实例化具体的bean(填充属性和初始化)
        getRegistry().put(name, beanDefinition);
    }

    private void processProperty(Element ele, BeanDefinition beanDefinition) {
        NodeList propertyNode = ele.getElementsByTagName("property");
        for (int i = 0; i < propertyNode.getLength(); i++) {
            Node node = propertyNode.item(i);
            if (node instanceof Element) {
                Element propertyEle = (Element) node;
                String name = propertyEle.getAttribute("name");

                String value = propertyEle.getAttribute("value");
                if (value != null && value.length() > 0) {
                    beanDefinition.getPropertyValues()
                            .addPropertyValue(PropertyValue.builder().name(name).value(value).build());
                    continue;
                }
                String ref = propertyEle.getAttribute("ref");
                if (ref == null || ref.length() <= 0) {
                    throw new RuntimeException("please check xml");
                }
                BeanReference beanReference = new BeanReference(ref);
                beanDefinition.getPropertyValues()
                        .addPropertyValue(PropertyValue.builder().name(name).value(beanReference).build());
            }
        }
    }
}
